package common;

import java.util.ArrayList;
import java.util.List;

import model.MClass;
import model.MClassAttribute;
import model.MDefinedObject;
import model.MObject;
import choco.Problem;
import constraint.MAnd;
import constraint.MConstraint;
import constraint.MEq;
import constraint.MLeq;
import constraint.MLt;
import constraint.MNeq;
import constraint.MObjectClass;
import constraint.MPredicate;
import expression.MBoundedIntVar;
import expression.MClassAttClassVar;
import expression.MClassAttUpperBound;
import expression.MClassCard;
import expression.MClassSelfCard;
import expression.MIntCst;
import expression.MIntExpr;
import expression.MIntVar;
import expression.MSum;


public class MCore {
	private String name;
	private List<MClass> classes;
	private List<MPredicate> predicates;
	private List<MPredicate> generated;
	private List<MIntVar> variables;
	private List<MDefinedObject> definedObjects;
	private List<MClassAttribute> attributes;
	private Problem problem;
	
	public MCore(String n) {
		name = n;
		classes = new ArrayList<MClass>();
		predicates = new ArrayList<MPredicate>();
		generated = new ArrayList<MPredicate>();
		variables = new ArrayList<MIntVar>();
		definedObjects = new ArrayList<MDefinedObject>();
		attributes = new ArrayList<MClassAttribute>();
	}

	public String getName() {
		return name;
	}
	
	public Problem getProblem() {
		return problem;
	}
	
	public String toText() {
		String s = "classes:\n";
		for (MClass c: classes) {
			s += "\t" + c.toText() + "\n";
		}
		s += "attributes:\n";
		for (MClassAttribute a: attributes) {
			s += "\t" + a.toText() + "\n";
		}
		s += "predicates:\n";
		for (MPredicate p: predicates) {
			s += "\t" + p.toText() + "\n";
		}
		s += "generated constraints:\n";
		for (MPredicate p: generated) {
			s += "\t" + p.toText() + "\n";
		}
		s += "variables:\n";
		for (MIntVar v: getVariables()) {
			s += "\t" + v.toText() + "\n";
		}
		return s;
	}
	
	public String toString() {
		String s = "classes:\n";
		for (MClass c: classes) {
			s += "\t" + c.toString() + "\n";
		}
		s += "attributes:\n";
		for (MClassAttribute a: attributes) {
			s += "\t" + a.toString() + "\n";
		}
		s += "predicates:\n";
		for (MPredicate p: predicates) {
			s += "\t" + p.toText() + "\n";
		}
		s += "generated constraints:\n";
		for (MPredicate p: generated) {
			s += "\t" + p.toText() + "\n";
		}
		s += "variables:\n";
		for (MIntVar v: getVariables()) {
			s += "\t" + v.toString() + "\n";
		}
		return s;
	}
	
	public List<MClass> getClasses() {
		return classes;
	}
	
	public MClass getClass(String n) {
		for (MClass c: classes) {
			if (c.getName().equals(n))
				return c;
		}
		return null;
	}
	
	public void addClass(String n, boolean a, MClass c) {
		MClass cl = new MClass(this, n, a, c);
		classes.add(cl);
	}
	
	public void addClassAttribute(String n) {
		MClassAttribute att = new MClassAttribute(this, n);
		attributes.add(att);		
	}
	
	public MClassAttribute getClassAttribute(String n) {
		for (MClassAttribute att: attributes) {
			if (n.equals(att.getName()))
				return att;
		}
		return null;
	}
	
	public List<MIntVar> getVariables() {
		return variables;
	}
	
	public MIntCst createIntCst(int value) {
		MIntCst cst = new MIntCst(this, value);
		return cst;
	}
	
	public MSum createMSum() {
		return new MSum(this);
	}
	
	public MEq createEq(MIntExpr left, MIntExpr right) {
		return new MEq(this, left, right);
	}

	public MLt createLt(MIntExpr left, MIntExpr right) {
		return new MLt(this, left, right);
	}

	public MLeq createLeq(MIntExpr left, MIntExpr right) {
		return new MLeq(this, left, right);
	}

	public MNeq createNeq(MIntExpr left, MIntExpr right) {
		return new MNeq(this, left, right);
	}
	
	public MObjectClass createObjectClass(MObject o, MClass c) {
		return new MObjectClass(o, c);
	}
	
	public MAnd createAnd(MConstraint[] csts) {
		MAnd cst = new MAnd(this);
		for (MConstraint c: csts) {
			cst.getConstraints().add(c);
		}
		return cst;
	}
	
	public void addPredicate(String n, String c, MConstraint cst) {
		MPredicate p = new MPredicate(this, n, c, cst);
		predicates.add(p);
	}
	
	public void defineObject(String n, MClass c) {
		definedObjects.add(c.addObject(n));
	}
	
	public MDefinedObject getObject(String n) {
		for (MDefinedObject o: definedObjects) {
			if (n.equals(o.getName()))
				return o;
		}
		return null;
	}
	
	public void addGeneratedConstraints() {
		/* 
		 * CLASSES
		 */
		for (MClass c: classes) {
			// C.isAbstract -> |C*| = 0
			if (c.isAbstract()) {
				MPredicate p = new MPredicate(this, c.getName() + "_abstract_card", "generated", createEq(c.getSelfCardinality(), createIntCst(0)));
				generated.add(p);
			}
			// |C| = |C*| + sum ( S: C.subclasses | |S| )
			{
				MSum right = createMSum();
				right.getParameters().add(c.getSelfCardinality());
				for (MClass sc: c.getSubClasses())
					right.getParameters().add(sc.getCardinality());
				MPredicate p = new MPredicate(this, c.getName() + "_card", "generated", createEq(c.getCardinality(), right));
				generated.add(p);
			}
			// (C.objects.size > 0) -> (C.objects.size <= |C*|)
			{
				if (c.getObjects().size() > 0) {
					MPredicate p = new MPredicate(this, c.getName() + "_objects", "generated", createLeq(new MIntCst(this, c.getObjects().size()), c.getSelfCardinality()));
					generated.add(p);
				}
			}
		}
	}
	
	public void initModel() {
		// creation des variables des ClassAttributes
		for (MClassAttribute a: attributes) {
			a.init();
		}
	}
	
	public void initVariables() {
		// init variables dependencies
		for (MIntVar v: getVariables()) {
			v.init();
		}
	}
	
	public static void addDependencies(List<MIntVar> vars, MIntVar var) {
		for (MIntVar v: var.getVarDepencies()) {
			if (!vars.contains(v))
				vars.add(v);
		}
	}
	
	public boolean computeUpperBound(MBoundedIntVar var) {
		// UpperBound
		problem = new Problem();
		var.reset();
		for (MIntVar v: var.getVarDepencies()) {
			v.reset();
		}
		for (MPredicate p: var.getPredDepencies()) {
			problem.post(p.getConstraint().constraint());
		}
		problem.maximize(var.getVariable(), false);
		System.out.println("(+) " + var.getVariable());

		return var.setUpperBound(var.getVariable().getSup());
	}

	public boolean computeLowerBound(MBoundedIntVar var) {
		// LowerBound
		problem = new Problem();
		var.reset();
		for (MIntVar v: var.getVarDepencies()) {
			v.reset();
		}
		for (MPredicate p: var.getPredDepencies()) {
			problem.post(p.getConstraint().constraint());
		}
		System.out.println(problem.constraintsToString());
		problem.minimize(var.getVariable(), false);
		System.out.println("(-) " + var.getVariable());
		
		return var.setLowerBound(var.getVariable().getInf());
	}
	
	public void computeBounds() {
		List<MBoundedIntVar> vars = new ArrayList<MBoundedIntVar>();
		
		for (MIntVar v: variables) {
			if (v instanceof MBoundedIntVar) {
				vars.add((MBoundedIntVar)v);
			}
		}
		
		while (vars.size() > 0) {
			MBoundedIntVar var = vars.get(0);
			System.out.println("> " + var);
			vars.remove(0);

			if ((var instanceof MClassCard) || (var instanceof MClassSelfCard)) {
				computeUpperBound(var);
				computeLowerBound(var);
			}
			else if (var instanceof MClassAttUpperBound) {
				MClassAttUpperBound att = (MClassAttUpperBound)var;
				
				if (computeUpperBound(var)) {
					for (MClassAttClassVar v: att.getAttribute().getVariables()) {
						v.setUpperBound(var.getUpperBound());
						if (!vars.contains(v))
							vars.add(v);
					}
				}

				if (computeLowerBound(var)) {
					for (MClassAttClassVar v: att.getAttribute().getVariables()) {
						v.setLowerBound(var.getLowerBound());
						if (!vars.contains(v))
							vars.add(v);
					}
				}
			}
		}
	}	
}
